/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is part of the Contiki operating system.
 *
 */

#include "contiki.h"
#include "contiki-lib.h"
#include "contiki-net.h"
#include "net/ip/uip.h"
#include "net/rpl/rpl.h"
#include "net/linkaddr.h"

#include "net/netstack.h"
#include "dev/button-sensor.h"
#include "dev/serial-line.h"
#if CONTIKI_TARGET_Z1
#include "dev/uart0.h"
#else
#include "dev/uart1.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include "collect-common.h"
#include "collect-view.h"
#include "ffz/lb/config_neighbor.h"

#define NODE_NUMS 21
#define CONSENS_NUMS 4
#define RANDOM_SEED 100

#define DEBUG DEBUG_PRINT
#include "net/ip/uip-debug.h"

#define UIP_IP_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN])

#define UDP_CLIENT_PORT 8775
#define UDP_SERVER_PORT 5688

#define HEX_DOUBLE 256

static struct uip_udp_conn *server_conn;

static uip_ipaddr_t sending_ipaddr;
static uip_ipaddr_t broadcast_ipaddr;
static uint16_t reqCursor = 1;
static uint8_t sendingBuffer[SENDING_BUFFER_LEN];

// #define COFF_CLOCK_MS 8

uint8_t id = 0;

#define SERVER_REPLY 0

PROCESS(udp_client_process, "UDP server process");
AUTOSTART_PROCESSES(&udp_client_process, &collect_common_process);
/*---------------------------------------------------------------------------*/
void collect_common_set_sink(void)
{
}
/*---------------------------------------------------------------------------*/
void collect_common_net_print(void)
{
  printf("I am sink!\n");
}
/*---------------------------------------------------------------------------*/
void collect_common_send(void)
{
  /* Server never sends */
}
/*---------------------------------------------------------------------------*/
void collect_common_net_init(void)
{
#if CONTIKI_TARGET_Z1
  uart0_set_input(serial_line_input_byte);
#else
  uart1_set_input(serial_line_input_byte);
#endif
  serial_line_init();

  PRINTF("I am sink!\n");
}
/*---------------------------------------------------------------------------*/
static void
tcpip_handler(void)
{
  uint8_t *appdata;
  linkaddr_t sender;
  uint8_t seqno;
  uint8_t hops;
  uint8_t data_len;

  // PRINTF("tcp_handler ");
  // PRINT6ADDR(&UIP_IP_BUF->srcipaddr);
  // PRINTF("\n");
  if (uip_newdata())
  {
    // PRINTF("uip_newdata \n");
    data_len = uip_datalen();
    appdata = (uint8_t *)uip_appdata;
    uint8_t actionType = *appdata;
    if (data_len == 46)
    {
      sender.u8[0] = UIP_IP_BUF->srcipaddr.u8[15];
      sender.u8[1] = UIP_IP_BUF->srcipaddr.u8[14];
      seqno = *appdata;
      hops = uip_ds6_if.cur_hop_limit - UIP_IP_BUF->ttl + 1;
      collect_common_recv(&sender, seqno, hops,
                          appdata + 2, uip_datalen() - 2);
    }
    // PRINT SENSOR DATA
    else
    {
      if (actionType == SENDING_ACTION_REQ)
      {
        uint8_t senderId;
        uint16_t reqId;
        uint16_t data;
        memcpy(&senderId, appdata + 1, 1);
        memcpy(&reqId, appdata + 2, 2);
        memcpy(&data, appdata + 5, 2);
        PRINTF("REQ_FINISH:%d,%d,%d,%d\n", reqId, actionType, senderId, data);
      }
      else if (actionType == SENSOR_CONSENSUS_REQ)
      {
        memcpy(sendingBuffer, appdata, CONSENS_PACKET_LEN);
        uip_udp_packet_sendto(server_conn, sendingBuffer, CONSENS_PACKET_LEN,
                              &broadcast_ipaddr, UIP_HTONS(UDP_CLIENT_PORT));
      }
    }
#if SERVER_REPLY
    PRINTF("DATA sending reply\n");
    uip_ipaddr_copy(&server_conn->ripaddr, &UIP_IP_BUF->srcipaddr);
    uip_udp_packet_send(server_conn, "Reply", sizeof("Reply"));
    uip_create_unspecified(&server_conn->ripaddr);
#endif
  }
}
/*---------------------------------------------------------------------------*/
static void
print_local_addresses(void)
{
  int i;
  uint8_t state;

  PRINTF("Server IPv6 addresses: ");
  for (i = 0; i < UIP_DS6_ADDR_NB; i++)
  {
    state = uip_ds6_if.addr_list[i].state;
    if (state == ADDR_TENTATIVE || state == ADDR_PREFERRED)
    {
      PRINT6ADDR(&uip_ds6_if.addr_list[i].ipaddr);
      PRINTF("\n");

      id = (&uip_ds6_if.addr_list[i].ipaddr)->u16[7] / HEX_DOUBLE;
      /* hack to make address "final" */
      if (state == ADDR_TENTATIVE)
      {
        uip_ds6_if.addr_list[i].state = ADDR_PREFERRED;
      }
    }
  }
  PRINTF("id = %d\n", id);
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(udp_client_process, ev, data)
{
  static struct etimer sendingRequestTimer;
  uip_ipaddr_t ipaddr;
  struct uip_ds6_addr *root_if;

  PROCESS_BEGIN();
  srand(RANDOM_SEED);

  PROCESS_PAUSE();

  etimer_set(&sendingRequestTimer, CLOCK_CONF_SECOND * REQ_SENDING_START);
  SENSORS_ACTIVATE(button_sensor);

  PRINTF("UDP server started\n");

#if UIP_CONF_ROUTER
  uip_ip6addr(&ipaddr, 0xaaaa, 0, 0, 0, 0, 0, 0, 1);
  /* uip_ds6_set_addr_iid(&ipaddr, &uip_lladdr); */
  uip_ds6_addr_add(&ipaddr, 0, ADDR_MANUAL);
  root_if = uip_ds6_addr_lookup(&ipaddr);
  if (root_if != NULL)
  {
    rpl_dag_t *dag;
    dag = rpl_set_root(RPL_DEFAULT_INSTANCE, (uip_ip6addr_t *)&ipaddr);
    uip_ip6addr(&ipaddr, 0xaaaa, 0, 0, 0, 0, 0, 0, 0);
    rpl_set_prefix(dag, &ipaddr, 64);
    PRINTF("created a new RPL dag\n");
  }
  else
  {
    PRINTF("failed to create a new RPL DAG\n");
  }
#endif /* UIP_CONF_ROUTER */

  print_local_addresses();

  /* The data sink runs with a 100% duty cycle in order to ensure high
     packet reception rates. */
  NETSTACK_RDC.off(1);

  server_conn = udp_new(NULL, UIP_HTONS(UDP_CLIENT_PORT), NULL);
  udp_bind(server_conn, UIP_HTONS(UDP_SERVER_PORT));

  PRINTF("Created a server connection with remote address ");
  PRINT6ADDR(&server_conn->ripaddr);
  PRINTF(" local/remote port %u/%u\n", UIP_HTONS(server_conn->lport),
         UIP_HTONS(server_conn->rport));

  uip_create_linklocal_allnodes_mcast(&broadcast_ipaddr);
  *(sendingBuffer) = SENDING_NULL;
  uip_udp_packet_sendto(server_conn, sendingBuffer, 1,
                        &broadcast_ipaddr, UIP_HTONS(UDP_CLIENT_PORT));
  while (1)
  {
    PROCESS_YIELD();
    if (ev == tcpip_event)
    {
      tcpip_handler();
    }
    else if (ev == sensors_event && data == &button_sensor)
    {
      PRINTF("Initiaing global repair\n");
      rpl_repair_root(RPL_DEFAULT_INSTANCE);
    }
    else if (ev == 136)
    {
      // PRINTF("sink timer trigger \n");

      // PRINTF("DATA sending reply\n");
      // uip_ipaddr_copy(&server_conn->ripaddr, &UIP_IP_BUF->srcipaddr);
      // uip_udp_packet_send(server_conn, "Reply", sizeof("Reply"));
      // uip_create_unspecified(&server_conn->ripaddr);

      /* set target address */
      // uip_ip6addr(&sending_ipaddr, 0xaaaa, 0, 0, 0, 0x0212, 0x740b, 0x000b, 0x0b0b);
      uip_ip6addr(&sending_ipaddr, 0xaaaa, 0, 0, 0, 0x0212, 0x7400, 0x0000, 0x0000);
      uint8_t consensusId = rand();
      uint8_t senderId = rand();
      // [2, 4]
      senderId = senderId % (NODE_NUMS - 1) + 2;
      // [2, 3]
      consensusId = consensusId % CONSENS_NUMS + 2;
      // PRINTF("senderID : %u\n", senderId);
      (&sending_ipaddr)->u8[11] = consensusId;
      (&sending_ipaddr)->u8[13] = consensusId;
      (&sending_ipaddr)->u8[14] = consensusId;
      (&sending_ipaddr)->u8[15] = consensusId;
      // PRINT6ADDR(&sending_ipaddr);
      // PRINTF("\n");
      // latency_clock[senderId] = clock_time();
      // PRINTF("DEBUG latency_clock[%d] = %d \n", senderId, latency_clock[senderId]);
      PRINTF("REQ_START:%d,%d,%d\n", reqCursor,consensusId,senderId);
      *(sendingBuffer) = SENDING_ACTION_REQ;
      *(sendingBuffer + 1) = senderId;
      memcpy(sendingBuffer + 2, &reqCursor, 2);
      uip_udp_packet_sendto(server_conn, sendingBuffer, REQ_DOWN_PACKET_LEN,
                            &sending_ipaddr, UIP_HTONS(UDP_CLIENT_PORT));
      reqCursor++;

      etimer_set(&sendingRequestTimer, CLOCK_CONF_SECOND * REQ_SENDING_SPAN);
    }
  }

  PROCESS_END();
}
/*---------------------------------------------------------------------------*/
